from umqtt.simple import MQTTClient
from lib.config.index import mqtt_config
from lib.mqtt.msg import MQTTMsg
from lib.log.index import mqtt_logger
import asyncio
from utils import device
import sys
from utils.common import collect_gc,print_gc_info
import ssl
import json

context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)


class Client:
    def __init__(self):
        self.__server = mqtt_config.get_server()
        self.__ClientID = mqtt_config.get_ClientID() + "-" + device.get_id()
        self.__user = mqtt_config.get_user()
        self.__password = mqtt_config.get_password()
        self.__topic = mqtt_config.get_topic()
        self.__port = mqtt_config.get_port()
        self.__protocol = mqtt_config.get_protocol()
        self.__callback = None
        self.__keepalive = mqtt_config.get_keepalive()
        self.__ssl = True if mqtt_config.get_ssl() == 1 else False
        self.___last_will_topic = mqtt_config.get_lastwill_topic()
        self.__lastwill_msg = mqtt_config.get_lastwill_msg()

    def set_callback(self, callback):
        self.__callback = callback

    def connect(self, clientID=None, username=None, password=None):
        if username and password:
            self.__user = username
            self.__password = password

        if clientID:
            self.__ClientID = clientID

        client = MQTTClient(
            self.__ClientID,
            self.__server,
            self.__port,
            self.__user,
            self.__password,
            self.__keepalive,
            self.__ssl,
        )
        if self.___last_will_topic:
            client.set_last_will(self.___last_will_topic, self.__lastwill_msg, False)
        client.set_callback(self.handle_msg)

        if mqtt_config.is_debug():
            print(f"ssl: {self.__ssl}")
            print(f"mqtt username:\n{self.__user}\npassword:\n{self.__password}")
            mqtt_logger.log(
                "action",
                f"[{self.__ClientID}] ({self.__user}, {self.__password}) connect {self.__server}:{self.__port}, keepalive {self.__keepalive}",
            )
            print_gc_info("mqtt gc before:")
        collect_gc()
        print_gc_info("mqtt gc end:")
        client.connect()
        if mqtt_config.is_debug():
            print('Connected to MQTT Broker "%s"' % (self.__server))
        self.__client = client

    def ping(self):
        mqtt_logger.log("action", "ping")
        self.__client.ping()

    def subscribe(self):
        mqtt_logger.log("action", f"[{self.__ClientID}] subscribe [{self.__topic}]")
        self.__client.subscribe(self.__topic)

    def subscribeTopic(self, topic):
        mqtt_logger.log("action", f"[{self.__ClientID}] subscribe [{topic}]")
        self.__client.subscribe(topic)

    def publish(self, mqtt_msg, topic=None):
        mqtt_logger.log("publish", mqtt_msg)
        if not topic:
            topic = self.__topic
        json_str = json.dumps(mqtt_msg)
        if mqtt_config.is_debug():
            print("send message %s, topic: %s" % (json_str, topic))
        self.__client.publish(topic, json_str, qos=0)

    def handle_msg(self, topic, msg):
        mqtt_logger.log("received", msg)
        if mqtt_config.is_debug():
            print("received message %s on topic %s" % (msg, topic))
        if self.__callback:
            self.__callback(topic, msg)
        return (topic, msg)

    async def wait_msg(self):
        try:
            self.__client.check_msg()
        except Exception as e:
            if mqtt_config.is_debug():
                sys.print_exception(e)
            await asyncio.sleep(2)
            self.reconnect()

    def reconnect(self):
        mqtt_logger.log(
            "action", f"[{self.__ClientID}] reconnect {self.__server}:{self.__port}"
        )
        try:
            self.__client.disconnect()
            self.__client.reconnect()
        except Exception as e:
            mqtt_logger.log("reconnect error", str(e))
            if mqtt_config.is_debug():
                sys.print_exception(e)
            device.reboot()


async def demo():
    subscriber = Client()
    subscriber.connect()
    subscriber.subscribe()
    while True:
        asyncio.create_task(mqtt_wait_msg(subscriber))
        await asyncio.sleep_ms(1_000)


async def mqtt_wait_msg(subscriber):
    await subscriber.wait_msg()
    await asyncio.sleep(3)


if __name__ == "__main__":
    from lib.net.index import wlan

    wlan.connect()
    asyncio.run(demo())
